import { fileURLToPath, URL } from "node:url";
import fs from "node:fs";
import * as semver from "semver";
import chalk from "chalk";
import fetch from "node-fetch";
import path from "node:path";

const __dirname = fileURLToPath(new URL(".", import.meta.url));

const mirror = "https://npmmirror.com/";
const registry = "https://registry.npmmirror.com/";

const packageJsonFile = getPackageJsonFile();

function getPackageJsonFile() {
  if (process.argv.length > 2 && process.argv[2]) {
    return path.resolve(process.argv[2]);
  }
  return path.join(process.cwd(), "package.json");
}

function formatDate(time) {
  const date = new Date(time);
  const year = date.getFullYear();
  const month = (date.getMonth() + 1).toString().padStart(2, "0");
  const day = date.getDate().toString().padStart(2, "0");
  return `${year}-${month}-${day}`;
}

function isStableVersion(version) {
  for (const it of ["beta", "alpha", "dev", "rc", "pre", "next"]) {
    if (version.includes(it)) {
      return false;
    }
  }
  return true;
}

function getDeps() {
  const json = fs.readFileSync(packageJsonFile).toString();
  const { dependencies = {}, devDependencies = {} } = JSON.parse(json);
  return {
    dependencies,
    devDependencies,
  };
}

async function checkDep(packageName, currentVersion) {
  const url = `${registry}${packageName}`;
  const mirrorUrl = `${mirror}package/${packageName}`;
  try {
    const resp = await fetch(url);
    const packageInfo = await resp.json();
    const latestVersion = packageInfo["dist-tags"].latest;
    const time = formatDate(packageInfo.time[latestVersion]);

    // 获取当前主版本的最新版本号
    const [currentMajor] = currentVersion.split(".");
    const versionNames = Object.keys(packageInfo.versions)
      .filter(it => {
        const [major] = it.split(".");
        return major === currentMajor && isStableVersion(it);
      })
      .sort((a, b) => semver.compare(a, b));
    const currentMajorLatest = versionNames.at(-1);

    return {
      name: packageName,
      latest: latestVersion,
      currentMajorLatest,
      time,
      homepage: packageInfo.homepage || mirrorUrl,
      mirrorUrl,
    };
  } catch (e) {
    console.error(`[${chalk.red(packageName)}]`, e.message);
    return {
      name: packageName,
      latest: "unknown",
      currentMajorLatest: "unknown",
      time: "unknown",
      homepage: mirrorUrl,
      mirrorUrl,
    };
  }
}

async function checkDeps(deps) {
  const depNames = Object.keys(deps);
  if (depNames.length === 0) return [];
  const list = [];

  for await (const packageName of depNames) {
    let currentVersion = deps[packageName];
    if (/^(https?:)|(file:)/.test(currentVersion)) {
      // 文件和连接依赖不检查
      console.log(`${chalk.yellow(packageName.padEnd(50, "-") + "->")} ${chalk.bold.yellow("skip".padEnd(10, " "))} ${currentVersion}`);
      continue;
    }
    if (currentVersion.startsWith("^") || currentVersion.startsWith("~")) {
      currentVersion = currentVersion.slice(1);
    }

    const info = await checkDep(packageName, currentVersion);
    const update = semver.gt(info.latest, currentVersion);
    const logName0 = `${packageName}@${currentVersion} `;
    let logName;
    let logVersion;
    if (update) {
      logName = chalk.green(logName0.padEnd(50, "-") + "->");
      logVersion = chalk.green(info.latest.padEnd(10, " "));
    } else {
      logName = chalk.gray(logName0.padEnd(50, "-") + "->");
      logVersion = chalk.gray(info.latest.padEnd(10, " "));
    }

    console.log(`${logName} ${logVersion} ${info.homepage}`);
    list.push({ ...info, installed: currentVersion, update: update ? "U" : "" });
  }

  return list;
}

async function main() {
  if (!fs.existsSync(packageJsonFile)) {
    console.error(`package.json not found in ${packageJsonFile}`);
    return;
  }

  console.log("check file =>", packageJsonFile);
  const { dependencies, devDependencies } = getDeps();
  await checkDeps(dependencies);
  await checkDeps(devDependencies);

  // const prodDeps = await checkDeps(dependencies);
  // const devDeps = await checkDeps(devDependencies);

  // const props = ["name", "installed", "currentMajorLatest", "latest", "time", "update", "mirrorUrl"];
  // console.table(prodDeps, props);
  // console.table(devDeps, props);
}

main().catch(() => void 0);
